package netty.echo;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.util.Scanner;

/**
 * Instruction：
 * Author：@author MaLi
 */
public class EchoClient {
    private int serverPort;
    private String serverIp;

    public EchoClient(int serverPort, String serverIp) {
        this.serverPort = serverPort;
        this.serverIp = serverIp;
    }

    public void startClient() {
        //Step1: 创建组装器 - 用于配置客户端的 - 事件轮询器 - 通道 - 处理器
        Bootstrap bootstrap = new Bootstrap();
        //Step2: 创建轮询器 - 封装了Selector, 用于选择数据传输事件
        NioEventLoopGroup workerLoopGroup = new NioEventLoopGroup();
        bootstrap.group(workerLoopGroup);
        //Step3: 设置通道类型 - 这里使用了NIOSocket
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.remoteAddress(serverIp, serverPort);
        bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
        //Step4: 配置事件处理器 - 有数据IO的时候, 用于处理该IO的处理器
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new EchoClientHandler());
            }
        });
        //Step5: 连接服务器, 并配置了一个监听器, 用于在连接完成后, 回调lambda函数
        ChannelFuture channelFuture = bootstrap.connect().addListener((ChannelFuture futureListener) -> {
            if (futureListener.isSuccess()) {
                System.out.println("connection successful");
            } else {
                System.out.println("connection failure");
            }
        });
        try {
            //sync作用: 因为上面的连接到服务器上以及监听都是异步操作, 执行后马上返回, 可能连接还未完全建立, 所以sync在此等待一下
            channelFuture.sync();

            //StepX - 业务操作: 在连接完成之后, 获取到通道, 往通道里面写一些数据
            //获取通道
            Channel channel = channelFuture.channel();
            //获取标准输入
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入信息: ");
            while (scanner.hasNext()) {
                String msg = scanner.next();
                // 创建一个缓冲区, 用于存储待发送的信息
                ByteBuf buffer = channel.alloc().buffer();
                //保存数据到直接内存的缓冲区
                buffer.writeBytes(msg.getBytes());
                // 通过通道将数据发送出去
                channel.writeAndFlush(buffer);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            //由于上面有while循环, 这里不出异常或者主动杀掉进程, 就不会执行到, 但是作为一个关闭是必不可少的, 否则真正的关闭不释放文件描述符
            workerLoopGroup.shutdownGracefully();
        }
    }
    //该内部类是对通道的入站操作的处理Handler
    public static class EchoClientHandler extends ChannelInboundHandlerAdapter{
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            //收到数据
            ByteBuf in = (ByteBuf) msg;
            byte[] arr = new byte[in.readableBytes()];
            in.getBytes(0, arr);
            System.out.println("Data from Server: " + new String(arr));
            //读取完成之后,要主动释放掉该buffer
            in.release();
        }
    }

    public static void main(String[] args) {
        EchoClient client = new EchoClient(8888,"localhost");
        client.startClient();
    }
}
